﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using AbstractBugTracker;
using Jira;
using SmartAssembly;
using Utilities;

namespace JiraPresentation
{
    public sealed class JiraBugPresenter : IBugPresenter<JiraBug, JiraBugDef>
    {
        private const int c_MaxTitleLength = 255;
        private const int c_MaxMessageLength = 200;
        private const int c_MaxPropertyLength = 200;

        private readonly JiraBugTracker m_JiraBugTracker;

        public JiraBugPresenter(JiraBugTracker jiraBugTracker)
        {
            m_JiraBugTracker = jiraBugTracker;
        }

        public void PresentNewBug(ProcessedReport processedReport, JiraBugDef bugDef)
        {
            JiraProductLinkConfig jiraProductLinkConfig = m_JiraBugTracker.GetJiraProductLinkConfig(processedReport.ProductBuildRef.ProductRef);

            string title = GetTitle(processedReport.Report, bugDef.BugRef);
            bugDef.CleanAndSetTitle(title);
            string description = GetDescription(processedReport.Report, jiraProductLinkConfig);
            bugDef.CleanAndSetDescription(description);
        }

        private static string GetTitle(Report report, BugRef bugRef)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("{{SA}} {0}", report.HumanExceptionTypeName);
            if (bugRef.ExceptionReportHash != null)
            {
                sb.AppendFormat(" ({0})", bugRef.ExceptionReportHash);
            }
            sb.AppendFormat(" @ {0}.{1}(\u2026)", report.HumanMethodTypeName, report.HumanMethodName);
            string title = sb.ToString();
            string truncatedTitle = title.Truncate(c_MaxTitleLength);
            return truncatedTitle;
        }

        private string GetDescription(Report report, JiraProductLinkConfig jiraProductLinkConfig)
        {
            BugDescription bugDescription = new BugDescription();
            bugDescription.MethodTypeName = report.MethodTypeName;
            bugDescription.MethodName = report.MethodName;
            bugDescription.Exceptions =
                report.ReportedStackTrace.ReportedExceptions.Select(
                    reportedException =>
                    new ExceptionDescription
                        {
                            ExceptionTypeName = reportedException.ExceptionTypeName,
                            ExceptionMessage = reportedException.ExceptionMessage.Truncate(c_MaxMessageLength),
                            SourceCode = GetSourceCode(report, jiraProductLinkConfig, reportedException.ReportedStackFrames),
                            StackTrace = reportedException.ReportedStackFrames.Select(reportedStackFrame => reportedStackFrame.GetBestStackTraceLine()).ToArray()
                        }).ToArray();

            return JiraJsonSerializer.Serialize(bugDescription);
        }

        private string GetSourceCode(Report report, JiraProductLinkConfig jiraProductLinkConfig, IEnumerable<ReportedStackFrame> reportedStackFrames)
        {
            ReportedStackFrame reportedStackFrame = reportedStackFrames.FirstOrDefault(x => x.DocumentUrlOrNull != null && x.LineNumberOrNull != null);
            if (reportedStackFrame == null || String.IsNullOrEmpty(m_JiraBugTracker.JiraConfig.OpenGrokServer))
            {
                return null;
            }

            IList<OpenGrokSetting> openGrokSettings = jiraProductLinkConfig.OpenGrokSettings;

            DateTime buildDateUtc = report.BuildDateUtc;

            foreach (OpenGrokSetting openGrokSetting in openGrokSettings)
            {
                Regex regex = new Regex(openGrokSetting.RegEx, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                Match match = regex.Match(reportedStackFrame.DocumentUrlOrNull);
                if (match.Success)
                {
                    string sourceCode = string.Format("{0}/source/xref/{1}/Source/{2}?a=true&r=%7b{3}%7d#{4}",
                        m_JiraBugTracker.JiraConfig.OpenGrokServer,
                        openGrokSetting.OpenGrokProject, match.Groups[1].Value.Replace('\\', '/'), buildDateUtc.ToString("s") + "Z", reportedStackFrame.LineNumberOrNull);
                    return sourceCode;
                }
            }
            return null;
        }

        public string GetReportCommentFor(JiraBug bug, ProcessedReport processedReport)
        {
            Report report = processedReport.Report;

            BugDescription originalBugDescription = GetOriginalBugDescriptionOrDefault(bug);

            ReportComment comment = new ReportComment();
            comment.Url = "sareport://" + report.Id;

            // Only add the exception messages if they are different to those with which the issue was raised.
            List<string> originalExceptionMessages = originalBugDescription.Exceptions.Select(x => x.ExceptionMessage.Truncate(c_MaxMessageLength)).Distinct().ToList();
            List<string> currentExceptionMessages = report.ReportedStackTrace.ReportedExceptions.Select(x => x.ExceptionMessage.Truncate(c_MaxMessageLength)).Distinct().Select(x => JiraUtils.Clean(x, false)).ToList();
            if (!originalExceptionMessages.SequenceEqual(currentExceptionMessages))
            {
                comment.ExceptionMessages = currentExceptionMessages;
            }

            comment.ProductBuildVersion = processedReport.ProductBuildRef.ProductBuildVersion.ToString();
            comment.SubmitterIpOrGuid = report.SubmitterIpOrGuid;
            comment.TotalAttachmentSize = report.TotalAttachmentSize;

            // leave as null if no properties, so it does not get serialized
            var customProperties = report.GetCustomProperties();
            if (customProperties.Any())
            {
                comment.CustomProperties = new Dictionary<string, string>();
                foreach (var customProperty in customProperties)
                {
                    comment.CustomProperties.Add(customProperty.Key, customProperty.Value.Truncate(c_MaxPropertyLength));
                }
            }

            return JiraJsonSerializer.Serialize(comment);
        }

        private static BugDescription GetOriginalBugDescriptionOrDefault(JiraBug bug)
        {
            BugDescription ret;
            if (bug.Description != null && JiraJsonSerializer.TryPopulate(bug.Description, out ret))
            {
                return ret;
            }
            else
            {
                return new BugDescription();
            }
        }

        public string GetCausalBugCommentFor(JiraBug cause)
        {
            CausalBugComment comment = new CausalBugComment { CausedBy = cause.ToString() };
            return JiraJsonSerializer.Serialize(comment);
        }

        public string GetEffectBugCommentFor(JiraBug effect)
        {
            EffectBugComment comment = new EffectBugComment { Caused = effect.ToString() };
            return JiraJsonSerializer.Serialize(comment);
        }
    }
}
